"""
基于极值理论的流式数据异常检测

参考论文：SPOT (KDD'17)
https://doi.org/10.1145/3097983.3098144

主要特点：
1. 使用极值理论进行异常检测
2. 适用于流式数据
3. 自适应阈值计算
"""
from typing import Dict
from typing import List
from typing import Sequence

from ads_evt import biSPOT  # 双向SPOT算法实现
import numpy as np

from .base import Score
from .common import DecomposableScorer
from ..model.case import CaseData
from ..model.graph import Graph
from ..model.graph import Node


def spot(train_y: np.ndarray, test_y: np.ndarray, proba: float = 1e-4) -> np.ndarray:
    """
    评估测试数据中的每个值相对于训练数据定义的正常分布的违规程度
    
    Args:
        train_y: 训练数据，用于建立正常行为基线
        test_y: 测试数据，需要检测的数据
        proba: 异常概率阈值，默认为1e-4
    
    Returns:
        np.ndarray: 异常分数数组，分数越高表示异常程度越高
    """
    # 创建双向SPOT模型
    model = biSPOT(q=proba)
    # 使用训练数据拟合模型，并准备检测测试数据
    model.fit(init_data=train_y, data=test_y)
    # 初始化模型参数
    model.initialize()
    # 运行检测（不触发报警）
    results = model.run(with_alarm=False)
    
    # 计算每个时间点的异常分数
    scores: List[float] = []
    # 遍历每个时间点的上下阈值
    for index, (upper, lower) in enumerate(
        zip(results["upper_thresholds"], results["lower_thresholds"])
    ):
        # 计算阈值区间宽度
        width: float = upper - lower
        # 处理病态情况（上下阈值相等或倒置）
        if width <= 0:
            width = 1
            
        # 根据数据点与阈值的关系计算异常分数
        if test_y[index] > upper:  # 超过上阈值
            scores.append((test_y[index] - upper) / width)
        elif test_y[index] < lower:  # 低于下阈值
            scores.append((lower - test_y[index]) / width)
        else:  # 在正常范围内
            scores.append(0)

    return np.array(scores)


class SPOTScorer(DecomposableScorer):
    """
    基于SPOT算法的节点评分器
    
    特点：
    1. 继承自DecomposableScorer，支持并行处理
    2. 使用SPOT算法进行异常检测
    3. 可以处理双向异常（过高和过低）
    """

    def __init__(self, proba: float = 1e-4, **kwargs):
        """
        初始化SPOT评分器
        
        Args:
            proba: 异常概率阈值，默认为1e-4
            **kwargs: 传递给父类的额外参数
        """
        super().__init__(**kwargs)
        self._porba = proba

    def score_node(
        self,
        graph: Graph,
        series: Dict[Node, Sequence[float]],  # 所有节点的时间序列数据
        node: Node,                           # 待评分的节点
        data: CaseData,                       # 案例数据
    ) -> Score:
        """
        对单个节点进行评分
        
        工作流程：
        1. 提取节点的时间序列数据
        2. 分割训练集和测试集
        3. 使用SPOT算法计算异常分数
        4. 聚合分数并返回结果
        
        Returns:
            Score: 节点的异常评分，如果节点正常则返回None
        """
        # 将节点的时间序列转换为numpy数组
        series_y = np.array(series[node])
        # 分割训练集和测试集
        train_y: np.ndarray = series_y[: data.train_window]
        test_y: np.ndarray = series_y[-data.test_window :]
        # 使用SPOT算法计算异常分数
        scores = spot(train_y, test_y, proba=self._porba)
        # 使用聚合函数计算最终分数
        spot_score = self._aggregator(abs(scores))
        # 如果分数为0，表示节点正常
        if spot_score == 0:
            return None
        # 创建评分对象并返回
        score = Score(spot_score)
        score["spot-score"] = spot_score
        return score
